package de.uhlesoluschns.opensearch.provider;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.net.URLCodec;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.content.ContentValues;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.text.TextUtils;

public class XmlImporter {
	
	private static final String NS_OSD = "http://a9.com/-/spec/opensearch/1.1/";
	
	private DocumentBuilder builder;

	public XmlImporter() {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);
		factory.setExpandEntityReferences(false);
		factory.setIgnoringComments(true);
		factory.setIgnoringElementContentWhitespace(true);
		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	public ContentValues parse(Uri data) throws IOException, SAXException {
		InputStream in = null;
		try {
			URL url = new URL(data.toString());
			URLConnection urlConnection = url.openConnection();
			in = openConnectionCheckRedirects(urlConnection);
			Document doc = builder.parse(in);
			return parseOpenSearchDescription(doc.getDocumentElement());
		} catch (IOException e) {
			throw e;
		} catch (SAXException e) {
			throw e;
		} finally {
			try {
				in.close();
			} catch (Exception e) {}
		}
	}

	/**
	 * Handles redirects from HTTPS to HTTP
	 * 
	 * @see http://java.sun.com/j2se/1.4.2/docs/guide/deployment/deployment-guide/upgrade-guide/article-17.html
	 * @param c
	 * @return
	 * @throws IOException
	 */
	private InputStream openConnectionCheckRedirects(URLConnection c) throws IOException 
	{
	   boolean redir;
	   int redirects = 0;
	   InputStream in = null;
	   do 
	   {
	      if (c instanceof HttpURLConnection) 
	      {
	         ((HttpURLConnection) c).setInstanceFollowRedirects(false);
	      }
	      // We want to open the input stream before getting headers
	      // because getHeaderField() et al swallow IOExceptions.
	      in = c.getInputStream(); 
	      redir = false; 
	      if (c instanceof HttpURLConnection) 
	      {
	         HttpURLConnection http = (HttpURLConnection) c;
	         int stat = http.getResponseCode();
	         if (stat >= 300 && stat <= 307 && stat != 306 &&
	            stat != HttpURLConnection.HTTP_NOT_MODIFIED) 
	         {
	            URL base = http.getURL();
	            String loc = http.getHeaderField("Location");
	            URL target = null;
	            if (loc != null) 
	            {
	               target = new URL(base, loc);
	            }
	            http.disconnect();
	            // Redirection should be allowed only for HTTP and HTTPS
	            // and should be limited to 5 redirections at most.
	            if (target == null || !(target.getProtocol().equals("http")
	               || target.getProtocol().equals("https"))
	               || redirects >= 5)
	            {
	               throw new SecurityException("illegal URL redirect");
	            }
	            redir = true;
	            c = target.openConnection();
	            redirects++;
	         }
	      }
	   } 
	   while (redir);
	   return in;
	}
	
	
	private enum OsdTags {
		ShortName,
		Description,
		InputEncoding,
		Image,
		Url
	}
	
	private static final String TYPE_SEARCH = "text/html";
	private static final String TYPE_SUGGESTION = "application/x-suggestions+json";
	
	private String language = null;
	
	private ContentValues parseOpenSearchDescription(Element root) {
		ContentValues engine = new ContentValues();
		NodeList childs = root.getChildNodes();
		for(int i=0; i<childs.getLength(); i++) {
			Node childNode = childs.item(i);
			if (childNode instanceof Element) {
				String ns = childNode.getNamespaceURI();
				//XXX force correct NS?
				if (true || ns == null || NS_OSD.equals(ns)) {
					Element elt = (Element) childNode;
					try {
						OsdTags tag = OsdTags.valueOf(elt.getLocalName());
						switch (tag) {
						case ShortName:
							engine.put("shortName", elt.getFirstChild()
									.getNodeValue().trim());
							break;
						case Description:
							engine.put("description", elt.getFirstChild()
									.getNodeValue().trim());
							break;
						case InputEncoding:
							engine.put("inputEncoding", elt.getFirstChild()
									.getNodeValue().trim());
							break;
						case Image:
							engine.put("image", elt.getFirstChild()
									.getNodeValue().trim());
							break;
						case Url:
							String type = elt.getAttribute("type").trim();
							String template = elt.getAttribute("template")
									.trim();

							Uri url = Uri.parse(template);
							Uri.Builder builder = url.buildUpon();

							//read params
							for (Node paramNode = elt.getFirstChild(); paramNode != null; paramNode = paramNode
									.getNextSibling()) {
								if (paramNode instanceof Element) {
									Element paramElt = (Element) paramNode;
									if ("Param".equals(paramElt.getTagName())) {
										String paramName = paramElt
												.getAttribute("name").trim();
										String paramValue = paramElt
												.getAttribute("value").trim();
										builder.appendQueryParameter(paramName,
												paramValue);
									}
								}
							}
							url = builder.build();

							if (TYPE_SEARCH.equals(type)) {
								String method = elt.hasAttribute("method") ? elt
										.getAttribute("method")
										: null;
								if (method == null
										|| "get".equals(method.toLowerCase())) {
									engine.put("searchUrl",
											replaceSearchVariables(url
													.toString()));
								} else {
									//TODO notify user
								}
							} else if (TYPE_SUGGESTION.equals(type)) {
								engine.put("suggestionUrl",
										replaceSearchVariables(url.toString()));
							}
							break;
						}
					} catch (IllegalArgumentException e) {/* Unknown Tag, that's OK */}
				}
			}
		}
		
		if(language != null) {
			engine.put("shortName", engine.getAsString("shortName") + " (" + language + ")");
		}
		
		return engine;
	}
	
	private String replaceSearchVariables(String in) {
		String lang = Locale.getDefault().getLanguage();
		if(TextUtils.isEmpty(lang)) {
			 lang = "en";
		}
		
		if(in.contains("{language}") || in.contains("%7Blanguage%7D")) {
			language = lang;
		}
		
		//replace language
		in = in.replaceAll("\\{language\\}", lang);
		in = in.replaceAll("%7Blanguage%7D", lang);
		//replace searchTerms
		in = in.replaceAll("\\{searchTerms\\}", "%s");
		in = in.replaceAll("%7BsearchTerms%7D", "%s");
		
		//replace unknown
		in = in.replaceAll("\\{.*\\}", "");
		in = in.replaceAll("%7B.*%7D", "");
		return in;
	}
	
	public Bitmap loadImage(String source) throws MalformedURLException, DecoderException {
		if(source == null) {
			return null;
		}
		if(source.startsWith("http")) {
			try {
				URL iconUrl = new URL(source);
				InputStream is = iconUrl.openStream();
				return BitmapFactory.decodeStream(is);
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else if(source.startsWith("data")) {
			//base 64
			int pos = source.indexOf("base64,");
			if(pos > 0) {
				pos += "base64,".length();
				byte [] data = new byte[source.length() - pos];
				source.getBytes(pos, source.length(), data, 0);
				data = Base64.decodeBase64(data);
				return BitmapFactory.decodeByteArray(data, 0, data.length);
			} else {
				//url encoded binary data
				pos = source.indexOf(",");
				if(pos > 0) {
					pos += ",".length();
					byte [] data = new byte[source.length() - pos];
					source.getBytes(pos, source.length(), data, 0);
					data = URLCodec.decodeUrl(data);
					return BitmapFactory.decodeByteArray(data, 0, data.length);
				}
			}
		}
		return null;
	}
}
